Given an undirected graph with maximum degree , find a graph coloring using at most colors.
For example:
This graph's maximum degree () is 3, so we have 4 colors (). Here's one possible coloring:
Graphs are represented by a list of node objects, each with a label, a set of neighbors, and a color:
class GraphNode:
def __init__(self, label):
self.label = label
self.neighbors = set()
self.color = None
a = GraphNode('a')
b = GraphNode('b')
c = GraphNode('c')
a.neighbors.add(b)
b.neighbors.add(a)
b.neighbors.add(c)
c.neighbors.add(b)
graph = [a, b, c]
We go through the nodes in one pass, assigning each node the first legal color we find.
How can we be sure we'll always have at least one legal color for every node? In a graph with maximum degree , each node has at most neighbors. That means there are at most colors taken by a node's neighbors. And we have colors, so there's always at least one color left to use.
When we color each node, we're careful to stop iterating over colors as soon as we find a legal color.
def color_graph(graph, colors):
for node in graph:
if node in node.neighbors:
raise Exception('Legal coloring impossible for node with loop: %s' %
node.label)
# Get the node's neighbors' colors, as a set so we
# can check if a color is illegal in constant time
illegal_colors = set([
neighbor.color
for neighbor in node.neighbors
if neighbor.color
])
# Assign the first legal color
for color in colors:
if color not in illegal_colors:
node.color = color
break
time where is the number of nodes and is the number of edges.
The runtime might not look linear because we have outer and inner loops. The trick is to look at each step and think of things in terms of the total number of edges () wherever we can:
Putting all the steps together, our complexity is .
What about space complexity? The only thing we're storing is the illegal_colors set. In the worst case, all the neighbors of a node with the maximum degree () have different colors, so our set takes up space.
The lowest number of colors we can use to legally color a graph is called the chromatic number.
There's no known polynomial time solution for finding a graph’s chromatic number. It might be impossible, or maybe we just haven’t figured out a solution yet.
We can't even determine in polynomial time if a graph can be colored using a given colors. Even if is as low as 3.
We care about polynomial time solutions ( raised to a constant power, like ) because for large s, polynomial time algorithms are more practical to actually use than higher runtimes like exponential time (a constant raised to the power of , like ). Computer scientists usually call algorithms with polynomial time solutions feasible, and problems with worse runtimes intractable.
The problem of determining if a graph can be colored with colors is in the class of problems called NP (nondeterministic polynomial time). This means that in polynomial time, we can verify a solution is correct but we can’t come up with a solution. In this case, if we have a graph that's already colored with colors we verify the coloring uses colors and is legal, but we can't take a graph and a number and determine if the graph can be colored with colors.
If you can find a solution or prove a solution doesn't exist, you'll win a $1,000,000 Millennium Problem Prize.
For coloring a graph using as few colors as possible, we don’t have a feasible solution. For real-world problems, we'd often need to check so many possibilities that we’ll never be able to use brute-force no matter how advanced our computers become.
One way to reliably reduce the number of colors we use is to use the greedy algorithm but carefully order the nodes. For example, we can prioritize nodes based on their degree, the number of colored neighbors they have, or the number of uniquely colored neighbors they have.
We used a greedy approach to build up a correct solution in one pass through the nodes.
This brought us close to the optimal runtime, but we also had to take that last step of iterating over the colors only until we find a legal color. Sometimes stopping a loop like that is just a premature optimization that doesn't bring down the final runtime, but here it actually made our runtime linear!
Do you have an answer?
Wanna review this one again later? Or do you feel like you got it all?
Mark as done Pin for review laterReset editor
Powered by qualified.io